#include "entity_search_cache.hh"
#include "module_frame.hh"
#include <stdexcept>

EntitySearchCache::EntitySearchCache(std::uint32_t entityType)
    : roundIndex_(0), entityType_(entityType), dirty_(true) {
}

EntitySearchCache::EntitySearchCache(EntitySearchCache && rht) {
    entityIDs_ = rht.entityIDs_;
    entityIDSet_ = rht.entityIDSet_;
    roundIndex_ = rht.roundIndex_;
    entityType_ = rht.entityType_;
    dirty_ = rht.dirty_;
    rht.entityIDSet_.clear();
    rht.entityIDs_.clear();
}

EntitySearchCache::~EntitySearchCache() {
}

bool EntitySearchCache::search(const SearchInfo & searchInfo) {
    switch (searchInfo.searchType) {
        case SearchType::NONE: {
            tryRebuildIDArray();
            try {
                if (searchInfo.method) {
                    searchInfo.method(entityType_, entityIDs_, searchInfo.userData);
                } else {
                    ModuleRef.getFramework()->onEntityFound(entityType_, entityIDs_, searchInfo.userData);
                }
            } catch (std::exception& e) {
                errorlog << "Exception in searching callback, reason:" << e.what() << endlog;
                return false;
            }
            break;
        }
        case SearchType::ROUNDROBIN:
            return searchRoundRobin(searchInfo);
        default:
            errorlog << "Search type is invalid, type:" << static_cast<std::uint32_t>(searchInfo.searchType) << endlog;
            return false;
    }
    return true;
}

std::size_t EntitySearchCache::size() {
    return entityIDs_.size();
}

const EntityIDVector & EntitySearchCache::getEntityIDs() const {
    return entityIDs_;
}

void EntitySearchCache::add(std::uint64_t entityID) {
    auto it = entityIDSet_.find(entityID);
    if (it != entityIDSet_.end()) {
        warnlog << "Re-cache the same entity, entity ID:" << entityID << ", cache type:" << entityType_ << endlog;
        return;
    }
    entityIDSet_.insert(entityID);
    dirty_ = true;
}

void EntitySearchCache::remove(std::uint64_t entityID) {
    auto it = entityIDSet_.find(entityID);
    if (it == entityIDSet_.end()) {
        warnlog << "Remove an inexist entity, entity ID:" << entityID << ", cache type:" << entityType_ << endlog;
        return;
    }
    entityIDSet_.erase(it);
    dirty_ = true;
}

bool EntitySearchCache::searchRoundRobin(const SearchInfo & searchInfo) {
    static EntityIDVector entityIDs;
    if (entityIDSet_.empty()) {
        return false;
    }
    tryRebuildIDArray();
    entityIDs.clear();
    if (roundIndex_ >= entityIDs_.size()) {
        roundIndex_ = 0;
    }
    entityIDs.push_back(entityIDs_[roundIndex_]);
    try {
        if (searchInfo.method) {
            searchInfo.method(entityType_, entityIDs, searchInfo.userData);
        } else {
            ModuleRef.getFramework()->onEntityFound(entityType_, entityIDs_, searchInfo.userData);
        }
    } catch (std::exception& e) {
        errorlog << "Exception in searching callback, reason:" << e.what() << endlog;
    }
    roundIndex_ += 1;
    return true;
}

void EntitySearchCache::tryRebuildIDArray() {
    // TODO tuning it!, PERF ISSUE
    if (dirty_) {
        entityIDs_.clear();
        for (auto id : entityIDSet_) {
            entityIDs_.push_back(id);
        }
        dirty_ = false;
    }
}

EntitySearchCacheManager::EntitySearchCacheManager() {
}

bool EntitySearchCacheManager::search(std::uint32_t entityType, SearchType searchType, SearchMethod method,
    std::time_t timeout, std::uint64_t userData) {
    searchTypeSet_.insert(entityType);
    SearchInfo searchInfo;
    searchInfo.searchType = searchType;
    searchInfo.method = method;
    searchInfo.userData = userData;
    // ISSUE when call once will fail forever
    auto it = cacheMap_.find(entityType);
    if ((it != cacheMap_.end()) && (it->second.size() != 0)) {
        // find in local cache
        it->second.search(searchInfo);
    } else {
        // Records search info. for each undergoing searching
        searchCallMap_.emplace(std::make_pair(sequence_, std::move(searchInfo)));
        auto& pendings = pendingSearches_[entityType];
        if (pendings.empty()) {
            // First time to query in locators
            if (!startFirstSearch(entityType, searchType, timeout)) {
                return false;
            }
        }
        // Records the sequence number of searching call
        pendingSearches_[entityType].push_back(sequence_);
        sequence_ += 1;
    }
    return true;
}

bool EntitySearchCacheManager::startFirstSearch(std::uint32_t entityType, SearchType searchType,
    std::time_t timeout) {
    // searching from locator
    if (!ModuleRef.searchEntity(entityType, 0, static_cast<std::uint32_t>(searchType))) {
        errorlog << "Search entity failed, entity type:" << entityType << endlog;
        return false;
    }
    // Fires a timer to monitor the failure searching
    auto timerID = ModuleRef.startTimer(timeout,
        [&](TimerHandle timerID, UserData userData) {
            auto searchEntityType = static_cast<std::uint32_t>(userData);
            if (updatePendingSearch(searchEntityType)) {
                ModuleRef.cancelTimer(timerID);
            } else {
                //verblog << "Search type:" << searchEntityType << endlog;
                ModuleRef.searchEntity(searchEntityType, 0, 0);
            }
        },
        entityType);
    if (timerID == INVALID_TIMER_ID) {
        errorlog << "Start searching timer failed, cache type:" << entityType << endlog;
        return false;
    }
    return true;
}

void EntitySearchCacheManager::update(std::uint32_t entityType, const EntityIDVector & entityIDs, CacheOP cacheOP) {
    if (entityIDs.empty()) {
        return;
    }
    if (cacheMap_.find(entityType) == cacheMap_.end()) {
        auto result = cacheMap_.emplace(std::make_pair(entityType, std::move(EntitySearchCache(entityType))));
        if (!result.second) {
            errorlog << "Add new cache failed, cache type:" << entityType << endlog;
            return;
        }
    }
    auto it = cacheMap_.find(entityType);
    if (it == cacheMap_.end()) {
        errorlog << "Try to update inexisting cache, cache type:" << entityType << endlog;
    } else {
        if (cacheOP == CacheOP::ADD) {
            for (auto entityID : entityIDs) {
                it->second.add(entityID);
            }
        } else if (cacheOP == CacheOP::REMOVE) {
            for (auto entityID : entityIDs) {
                it->second.remove(entityID);
            }
        } else {
            errorlog << "Invalid cache OP, OP type:" << static_cast<std::uint32_t>(cacheOP) << endlog;
        }
    }
}

const EntityIDVector& EntitySearchCacheManager::getEntityIDs(std::uint32_t entityType) {
    static EntityIDVector NullVector;
    auto it = cacheMap_.find(entityType);
    if (it == cacheMap_.end()) {
        return NullVector;
    }
    return it->second.getEntityIDs();
}

void EntitySearchCacheManager::research() {
    cacheMap_.clear();
    for (auto entityType : searchTypeSet_) {
        search(entityType, SearchType::NONE, nullptr, 5000, 0);
    }
}

bool EntitySearchCacheManager::updatePendingSearch(std::uint32_t entityType) {
    auto pendingsIt = pendingSearches_.find(entityType);
    if (pendingsIt == pendingSearches_.end()) {
        return false;
    }
    auto cacheIt = cacheMap_.find(entityType);
    if (cacheIt == cacheMap_.end()) {
        errorlog << "Entity's searching timeout for entity type:" << entityType << endlog;
        return false;
    } else {
        for (auto sequence : pendingsIt->second) {
            // call method
            auto it = searchCallMap_.find(sequence);
            cacheIt->second.search(it->second);
            searchCallMap_.erase(it);
        }
    }
    // Removes pending searchings
    pendingSearches_.erase(pendingsIt);
    return true;
}

SearchInfo::SearchInfo()
    : searchType(SearchType::NONE), userData(0) {
}

SearchInfo::SearchInfo(SearchInfo && rht) {
    method = rht.method;
    searchType = rht.searchType;
    userData = rht.userData;
    rht.method = nullptr;
}
